今天我們介紹 abstract factory 模式。本篇包括:
GoF 如此說:
(Abstract factory 模式)為建立一組相關或相依的物件提供一個介面,而且無須指定它們的具體類別。
也就是透過一種協調的方式來讓特定的物件實體化。我們來看個例子吧!
這個例子是一個電腦系統(ApControl
):顯示並列印出取自資料庫的幾何形狀。Here's the kicker: 系統必須要能判斷出裝置的配置優劣,而選擇出適合的驅動程式:低配置與高配置兩種。如下表。
驅動功能 | 低配置裝置下 | 高配置裝置下 |
---|---|---|
顯示 | LRDD 低解析度顯示驅動程式 |
HRDD 低解析度列印驅動程式 |
列印 | LRPD 高解析度顯示驅動程式 |
HRPD 高解析度列印驅動程式 |
在本例中,低解析度顯示驅動程式必然配上低解析度列印驅動程式,反之亦然。但並非實際狀況都是如此。
最直覺的做法是用 switch-case 再 ApControl
中直接做判斷。但想單然爾這樣做是不好的:這麼做會讓驅動程式的規則與實際使用混雜在一起。如此會造成:
這將會增加往後的維護成本,而且遇到需求變化時,無法靈活調整。
第二種方案是依照配置高低,再將 ApControl
向下衍生 LowResApControl
與 HighResApControl
。(此時,ApControl
為抽象類別)。
這個方案也會有一些我們之前碰到的問題:新需求產生時,衍生類別可能爆炸增長。
當然,我們要用物件導向的一些基本原則去解決。我們知道,在這個案例中,在變化的是顯示驅動程式與列印驅動程式。於是我們將其抽出,然後封裝;之後再讓 ApControl
去使用它們(用聚合取代繼承)。
我們會得到下面的 UML 圖:
配上相對應的程式碼(尚未齊全):
class ApControl {
private myDisplayDriver;
private myPrintDriver;
public doDraw() {
// ...
// 讓 myDisplayDriver 對應到正確的顯示驅動程式
myDisplayDriver.draw();
}
public doPrint() {
// ...
// 讓 myPrintDriver 對應到正確的列印驅動程式
myPrintDriver.print();
}
}
我們接下來必須思考的就是程式碼裡頭的 comment:該如何建立合適的物件?
這裡的辦法就是藉由一個工廠物件 ResFactory
來協助驅動程式的建立。如此做有幾個好處:
ApControl
只要關注在如何使用合適的選擇驅動程式上。如此可達到(使用規則與實際使用的)職責分解。ApControl
負責使用驅動程式。我們知道 ResFactory
需要做兩件事:生產應該使用的顯示驅動程式與生產應該使用的列印驅動程式。因此,我們會衍生出 LowResFactory
與 HighResFactory
。看下圖就能理解。
將上圖配合原本的系統,結合起來就是我們將 abstract factory 模式實踐在此例上的 UML 圖:
當然,你也許也會注意到 LRDD
與 HRDD
不一定會共享同一個介面, LRPD
與 HRPD
也有一樣的問題。但是我們知道我們總是可以用 adapter 模式解決這個問題,所以我們就不再贅述。
ApControl
仍是需要以某些方式選擇適合的工廠產生適合的物件,這意指我們可能仍會有 switch-case 做一些判斷。
當然,要有判斷是一定的,但是此處的 switch-case 與 解決方案1 的 switch-case 還是有明顯的差異,差別就在於我們將某些職責抽離給工廠負責,這讓這裡的判斷句沒有讓規則與實際使用互相耦合,反倒是將其解耦了。
我們也可以增加一些相關的 config
檔,使主程式能夠較輕鬆地能取得正確的工廠物件。
以下是本模式的關鍵特徵:
項目 | 內容 |
---|---|
意圖 | 需要為特定的客戶或情況提供物件(或物件組) |
問題 | 需要實體化一組相關的物件 |
解決方案 | 協調物件組的建立 |
參與者與實作者 | AbstractFactory 為如何建立物件組每個成員定義介面。每個組都由 ConcreteFactory 進行建立 |
效果 | 將使用哪些物件與如何使用那些物件的邏輯分開 |
實作 | 定義一個抽象類別來指定建立哪些物件,為每個組實作具體類別 |
以下是 UML 圖:
下一篇我們將講到本書的 Chapter 14:設計模式的原則與策略。See ya!